#include <WindowsX.h>
#include <mh/app.hpp>
#include <mh/renderer.hpp>
#include <mh/network/socket_win32.hpp>
#include <mh/win32/timer.hpp>

namespace Mh
{
	static WinApp* winApp = nullptr;
	::HWND	g_hwnd;

	LRESULT CALLBACK WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
	{
		assert(winApp);
		return winApp->window_proc(hWnd, message, wParam, lParam);
	}

	WinApp::WinApp( HINSTANCE hInstance, LPTSTR lpCmdLine, int nCmdShow )
		: m_instance( hInstance ), m_resource( nullptr ), m_hid( nullptr ),
		m_paused(false), m_minimized(false), m_maximized(false), m_resizing(false), m_timer(nullptr)
	{
		assert(!winApp);

		this->set_client_width(640);
		this->set_client_height(480);

		m_timer = new TimerWin32();

		UNREFERENCED_PARAMETER(lpCmdLine);
		UNREFERENCED_PARAMETER(nCmdShow);

		winApp = this;
	}

	WinApp::~WinApp()
	{
		SafeDelete(&m_timer);

		winApp = nullptr;
	}

	bool WinApp::initialize( LPCTSTR title, uint width, uint height )
	{
		// Ignore the return value because we want to run the program even in the
	    // unlikely event that HeapSetInformation fails.
		{
			HRESULT hr = (::HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0) ? S_OK : E_FAIL );
			if (SUCCEEDED(hr))
				hr = ::CoInitializeEx(
				nullptr,
				COINIT_APARTMENTTHREADED//COINIT_MULTITHREADED
				);

			if( FAILED(hr) )
				return false;
		}

		if( !this->create_window( title, width, height ) )
			return false;

		if( !this->get_system_info() )
			return false;

		if( 0 != Network::SocketWin32::startup( MAKEWORD(2,2) ) )
			return false;

		return true;
	}

	void WinApp::finalize()
	{
		Network::SocketWin32::cleanup();

		if( m_hid )
		{
			m_hid->finalize();
			delete m_hid;
			m_hid = NULL;
		}
		
		if( m_resource )
		{
			m_resource->finalize();
			delete m_resource;
			m_resource = NULL;
		}
		
		::CoUninitialize();
	}

	LRESULT WinApp::window_proc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
	{
		LRESULT result;

		switch (message)
		{
		case WM_CREATE:
			result = 0;
			break;

		case WM_DESTROY:
			::PostQuitMessage(0);
			result = 0;
			break;

		case WM_ACTIVATE:
			if (LOWORD(wparam) == WA_INACTIVE)
			{
				m_timer->stop();
				m_paused = true;
			}
			else
			{
				m_timer->start();
				m_paused = false;
			}
			result = 0;
			break;

		// 화면 사이즈 관련
		case WM_SIZE:
			{
				uint width = LOWORD(lparam);
				uint height = HIWORD(wparam);

				set_client_width(width);
				set_client_height(height);

				Renderer* renderer = get_renderer();
				if (renderer != NULL)
				{
					switch (wparam)
					{
					case SIZE_MINIMIZED:
						m_paused = true;
						m_minimized = true;
						m_maximized = false;
						break;
					case SIZE_MAXIMIZED:
						m_paused = false;
						m_minimized = false;
						m_maximized = false;
						break;
					case SIZE_RESTORED:
						if (m_minimized)
						{
							m_minimized = false;
							m_paused = false;
						}
						else if (m_maximized)
						{
							m_maximized = false;
						}
						else
						{
						}
						break;
					}
				}
			}
			result = 0;
			break;

		case WM_ENTERSIZEMOVE:		// 크기변경 테두리를 클릭했을때
			m_paused = true;
			m_resizing = true;
			m_timer->stop();
			result = 0;
			break;

		case WM_EXITSIZEMOVE:		// 크기변경 테두리 클릭 후 놓을때
			{
				m_paused = false;
				m_resizing = false;
				m_timer->start();

				Renderer* renderer = get_renderer();
				if (renderer != nullptr)
				{
					uint width = get_client_width();
					uint height = get_client_height();

					renderer->on_resize(width, height);
				}
			}
			result = 0;
			break;

		// 이하 마우스 입력

		case WM_MOUSEMOVE:
			on_mouse_move(wparam, GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam));
			result = 0;
			break;

		case WM_LBUTTONUP:
		case WM_RBUTTONUP:
		case WM_MBUTTONUP:
			on_mouse_up(wparam, GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam));
			result = 0;
			break;

		case WM_LBUTTONDOWN:
		case WM_RBUTTONDOWN:
		case WM_MBUTTONDOWN:
			on_mouse_down(wparam, GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam));
			result = 0;
			break;

		case WM_LBUTTONDBLCLK:
		case WM_RBUTTONDBLCLK:
		case WM_MBUTTONDBLCLK:
			on_mouse_doubleclick(wparam, GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam));
			result = 0;
			break;

		case WM_MOUSEWHEEL:
			on_mouse_wheel(GET_WHEEL_DELTA_WPARAM(wparam), LOWORD(wparam), GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam));
			result = 0;
			break;

		default:
			result = DefWindowProc(hwnd, message, wparam, lparam);
		}

		return result;
	}

	void WinApp::on_mouse_move(WPARAM button_state, int x, int y)
	{
		// 클릭한 상태에서 이동중일때(=drag)
		if (::GetCapture() == m_hwnd)
		{
			
		}
	}

	void WinApp::on_mouse_up(WPARAM button_state, int x, int y)
	{
		::ReleaseCapture();
	}

	void WinApp::on_mouse_down(WPARAM button_state, int x, int y)
	{
		::SetCapture(m_hwnd);
	}

	void WinApp::on_mouse_doubleclick(WPARAM button_state, int x, int y)
	{

	}

	void WinApp::on_mouse_wheel(short wheel_delta, WORD button_state, int x, int y)
	{

	}

	bool WinApp::create_window( LPCTSTR title, uint width, uint height )
	{
		LPCTSTR classname = TEXT("MhWinApp");
		
		// Register the window class.
		{
			::WNDCLASSEXW wcex = {};
		
			wcex.cbSize        = sizeof(WNDCLASSEX);
			wcex.style         = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
			wcex.lpfnWndProc   = Mh::WndProc;
			wcex.cbWndExtra    = sizeof(LONG_PTR);
			wcex.hInstance     = m_instance;
			wcex.hCursor       = LoadCursor(NULL, IDC_ARROW);
			wcex.lpszClassName = classname;

			if( !RegisterClassExW(&wcex) )
				return false;
		}

		// create window
		{
			::RECT rt = { 0, 0, width, height };
			::AdjustWindowRect( &rt, WS_OVERLAPPEDWINDOW, FALSE );
			m_hwnd = ::CreateWindowExW(
				0,
				classname, title,
				WS_OVERLAPPEDWINDOW,
				CW_USEDEFAULT, CW_USEDEFAULT,
				rt.right - rt.left, rt.bottom - rt.top,
				::GetDesktopWindow(), NULL, m_instance,
				this );

			if( NULL == m_hwnd )
				return false;
		}

#if 0
		// Raw Input 등록
		{
			::RAWINPUTDEVICE rid[2];
			rid[0];
			if (FALSE == ::RegisterRawInputDevices(rid, 2, sizeof(::RAWINPUTDEVICE)))
				return false;
		}
#endif
		
		g_hwnd = m_hwnd;

		::ShowWindow( m_hwnd, SW_SHOW );

		return true;
	}
	
	bool WinApp::on_resize( int width, int height )
	{
		this->set_client_width(width);
		this->set_client_height(height);

		Renderer* renderer = get_renderer();
		if( renderer != NULL )
			return renderer->on_resize( width, height );
		else
			return true;
	}

	bool WinApp::get_system_info() const
	{
		::BOOL succeeded;
		::HW_PROFILE_INFO hpi;
		::SYSTEM_INFO si;
		
		// get System info
		{
			// Is process under WOW64?
			BOOL isWow64 = FALSE;

			// IsWow64Process 함수는 WindowsXP SP2 이후부터 지원하므로, 바로 호출하면 안되고
			// 커널 DLL에서 함수 주소를 얻어와서 처리해야함(귀찮게시리...)
			typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL);
			LPFN_ISWOW64PROCESS fnIsWow64Process;

			fnIsWow64Process = (LPFN_ISWOW64PROCESS)::GetProcAddress(
				::GetModuleHandle(TEXT("kernel32")),"IsWow64Process");
			if( NULL != fnIsWow64Process )
			{
				succeeded = (*fnIsWow64Process)( ::GetCurrentProcess(), &isWow64 );
				if( !succeeded )	// handle error
					return false;
			}

			if( isWow64 )
				::GetNativeSystemInfo( &si );
			else
				::GetSystemInfo( &si );

			// 이후 처리는 알아서...
			switch( si.wProcessorArchitecture )
			{
			case PROCESSOR_ARCHITECTURE_AMD64:
				break;
			case PROCESSOR_ARCHITECTURE_IA64:
				break;
			case PROCESSOR_ARCHITECTURE_INTEL:
				break;
			case PROCESSOR_ARCHITECTURE_UNKNOWN:
				break;
			}
		}

		// hardware profile
		{
			succeeded = ::GetCurrentHwProfile( &hpi );
			if( !succeeded )
				return false;

			// 쓸 일이 있을지는 모르겠지만 알아서...
		}

		// OS info
		{
			// GetVersionEx는 deprecated됨. VerifyVersionInfo() 나 IsWindows* 헬퍼 함수를 사용할 것
			// 관련글 http://msdn.microsoft.com/en-us/library/windows/desktop/dn302074.aspx
#if 0
			::OSVERSIONINFOEX osvi;

			ZeroMemory( &osvi, sizeof(::OSVERSIONINFOEX) );
			osvi.dwOSVersionInfoSize = sizeof(::OSVERSIONINFOEX);

			succeeded = ::GetVersionEx( (::OSVERSIONINFO*)&osvi );
			if( !succeeded )
				return false;

			// 버전을 통해 뭐 이것저것 처리...(렌더러라던가)
#endif		
		}

		return true;
	}
	
	int WinApp::run()
	{
		::MSG msg = { 0 };

		m_timer->reset();

		while( WM_QUIT != msg.message )
		{
			if( ::PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
			{
				TranslateMessage( &msg );
				DispatchMessage( &msg );
			}
			else
			{
				m_timer->tick();
				if (!m_paused)
				{
					Renderer* renderer = get_renderer();
					if (renderer)
					{
						renderer->begin_render();
						renderer->end_render();
					}
				}
				else
				{
					::Sleep(100);
				}
			}
		}

		return (int)msg.wParam;
	}

}		// namespace Mh